package com.xhji.web.mvc.interceptor;

import com.alibaba.fastjson.JSON;
import com.xhji.web.mvc.annotations.LogFilter;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;

@Slf4j
public class HandlerMethodLogInterceptor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements HandlerInterceptor, WebMvcConfigurer, MethodInterceptor, InitializingBean {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("start", System.currentTimeMillis());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long start = (long) request.getAttribute("start");
        Object[] args = (Object[]) request.getAttribute("args");
        Object result = request.getAttribute("result");
        boolean printArgs = false, printResult = false;
        if (handler instanceof HandlerMethod) {
            LogFilter filter = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), LogFilter.class);
            if (Objects.nonNull(filter)) {
                printArgs = filter.args();
                printResult = filter.result();
            }
        }
        Predicate<Object> filter = e -> e instanceof HttpServletRequest && e instanceof HttpServletResponse && e instanceof HttpSession && e instanceof MultipartFile;
        log.info("{}ms {}  args:{} result:{} ext:{}",
                System.currentTimeMillis() - start,
                request.getRequestURI(),
                printArgs ? JSON.toJSONString(Stream.of(Optional.ofNullable(args).orElse(new Object[]{})).map(e -> (e instanceof List && ((List) e).get(0) instanceof MultipartFile) ? null : e).filter(filter.negate()).toArray()) : null,
                printResult ? JSON.toJSONString(result) : null,request.getRemoteAddr());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this);
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        RequestMapping rest = AnnotationUtils.findAnnotation(invocation.getMethod(), RequestMapping.class);
        //记录参数
        HttpServletRequest request = getServletRequest();
        if (Objects.nonNull(rest) && Objects.nonNull(request)) {
            request.setAttribute("args", invocation.getArguments());
        }
        Object result = invocation.proceed();
        //记录返回值
        if (Objects.nonNull(rest) && Objects.nonNull(request)) {
            request.setAttribute("result", result);
        }
        return result;
    }

    @Override
    public void afterPropertiesSet() {
        this.advisor = new DefaultPointcutAdvisor(
                AnnotationMatchingPointcut.forClassAnnotation(RestController.class), this);
        this.setProxyTargetClass(true);
    }

    public static HttpServletRequest getServletRequest() {
        return Optional.ofNullable(RequestContextHolder.getRequestAttributes()).map(attr -> (ServletRequestAttributes) attr).map(ServletRequestAttributes::getRequest).orElse(null);
    }
}
